project('rizin', 'c',
  version: 'v0.9.0',
  license: 'LGPL-3.0-only',
  meson_version: '>=0.57.0',
  default_options: [
    'buildtype=debugoptimized',
    'b_vscrt=from_buildtype',
    'warning_level=1',
  ]
)

py3_exe = import('python').find_installation()
git_exe = find_program('git', required: false)
pkgconfig_mod = import('pkgconfig')

# Python scripts used during the build process
create_tags_rz_py = files('sys/create_tags_rz.py')
syscall_preprocessing_py = files('sys/syscall_preprocessing.py')
check_meson_subproject_py = files('sys/check_meson_subproject.py')
git_exe_repo_py = files('sys/meson_git_wrapper.py')
cmake_package_prefix_dir_py = files('sys/meson_cmake_prefix_dir.py')

is_static_build = get_option('static_runtime')
is_static_libs_only = get_option('default_library') == 'static'
if is_static_build and not is_static_libs_only
  error('Cannot use `static_runtime` when libraries are dynamically built. Set `--default-library=static` if you want to build statically.')
endif

# Get rizin version and compute various version parts
rizin_version = meson.project_version().split('v')[1]
rizin_version_split = rizin_version.split('.')
rizin_version_major = rizin_version_split[0].to_int()
rizin_version_minor = rizin_version_split[1].to_int()
rizin_version_patch = rizin_version_split[2].split('-')[0].to_int()
rizin_version_number = 1000000 * rizin_version_major + 1000 * rizin_version_minor + rizin_version_patch

repo = meson.current_source_dir()

rizin_libversion = '@0@.@1@'.format(rizin_version_major, rizin_version_minor)
message('rizin lib version: ' + rizin_libversion)

# system dependencies
cc = meson.get_compiler('c')
cc_native = meson.get_compiler('c', native: true)

platform_deps = []
platform_inc = ['.', 'librz', 'librz/include']
if host_machine.system() == 'windows'
  add_project_arguments('-DPSAPI_VERSION=1', language: 'c')
  platform_deps = [
    cc.find_library('ws2_32'),
    cc.find_library('wininet'),
    cc.find_library('psapi'),
  ]
endif
platform_inc = include_directories(platform_inc)

lib_name_suffix = []
lib_name_prefix = []
if is_static_libs_only and cc.get_id() == 'msvc'
  # On MSVC meson uses the .a suffix and lib as prefix, but cmake and other
  # tools expect a .lib suffix and no prefix
  # https://mesonbuild.com/FAQ.html#why-does-building-my-project-with-msvc-output-static-libraries-called-libfooa
  lib_name_suffix = 'lib'
  lib_name_prefix = ''
endif

if is_static_build
  if cc.get_id() == 'msvc'
    # Use -Db_vscrt=static_from_buildtype to avoid warnings due to multiple /M options
    if get_option('b_vscrt') == 'from_buildtype'
      warning('To avoid warnings due to multiple /M options, please also set -Db_vscrt=mt or -Db_vscrt=mtd.')
      add_project_arguments('/MT', language: 'c')
    endif
  else
    if cc.has_link_argument('-static')
      add_project_link_arguments('-static', language: 'c')
    endif
  endif
endif

# These two warnings enum compare and conversion are necessary for the Capstone compatibility header.
# Newer compilers will warn about the comparison of different enums (arm64 and aarch64) in the header.
# Hence, we have to disable them for the newer once.
# Older compilers don't have these warnings and should not be included with #pragma
if cc.has_argument('-Wenum-conversion')
  add_project_arguments('-DCC_SUPPORTS_W_ENUM_CONVERION', language: ['c', 'cpp'])
endif
if cc.has_argument('-Wenum-compare')
  add_project_arguments('-DCC_SUPPORTS_W_ENUM_COMPARE', language: ['c', 'cpp'])
endif

if cc.has_argument('-std=gnu99')
  add_project_arguments('-std=gnu99', language: ['c', 'cpp'])
elif cc.has_argument('--std=gnu99')
  add_project_arguments('--std=gnu99', language: ['c', 'cpp'])
elif cc.has_argument('-std=c99')
  add_project_arguments('-std=c99', language: ['c', 'cpp'])
elif cc.has_argument('--std=c99')
  add_project_arguments('--std=c99', language: ['c', 'cpp'])
endif

# Sanitize correct usage of rz_strf()
if cc.has_argument('-Werror=sizeof-pointer-memaccess')
  add_project_arguments('-Werror=sizeof-pointer-memaccess', language: ['c', 'cpp'])
endif

if cc.has_argument('-Wimplicit-fallthrough=3')
  add_project_arguments('-Wimplicit-fallthrough=3', language: ['c', 'cpp'])
endif

if cc.get_id() == 'clang-cl'
  if cc.has_argument('-fcommon')
    add_project_arguments('-fcommon', language: 'c')
  endif
  add_project_arguments('-D__STDC__=1', language: 'c')
  add_project_arguments('-D_CRT_DECLARE_NONSTDC_NAMES ', language: 'c')
  add_project_arguments('-D_CRT_SECURE_NO_WARNINGS', language: 'c')
  add_project_arguments('-D_CRT_NONSTDC_NO_DEPRECATE', language: 'c')
endif

if get_option('default_library') == 'shared'
  if cc.has_argument('-fvisibility=hidden')
    add_project_arguments('-fvisibility=hidden', language: 'c')
  endif
endif

add_project_arguments(['-DRZ_PLUGIN_INCORE=1'], language: 'c')
b_sanitize_opt = get_option('b_sanitize')
if (b_sanitize_opt.contains('address') or b_sanitize_opt.contains('undefined')) and cc.get_id() == 'clang'
  add_project_arguments('-shared-libasan', language: 'c')
  add_project_link_arguments('-shared-libasan', language: 'c')
  add_project_arguments('-shared-libasan', language: 'c', native: true)
  add_project_link_arguments('-shared-libasan', language: 'c', native: true)
endif

fs = import('fs')
rizin_prefix = get_option('prefix')
rizin_bindir = get_option('bindir')
rizin_libdir = get_option('libdir')
rizin_cmakedir = rizin_libdir / 'cmake'
rizin_incdir = get_option('includedir') / 'librz'
rizin_datdir = get_option('datadir')
extra_prefix = get_option('extra_prefix')
if host_machine.system() == 'windows'
  rizin_prefix = fs.as_posix(rizin_prefix).replace('/', '\\\\')
  rizin_bindir = fs.as_posix(rizin_bindir).replace('/', '\\\\')
  rizin_libdir = fs.as_posix(rizin_libdir).replace('/', '\\\\')
  rizin_cmakedir = fs.as_posix(rizin_cmakedir).replace('/', '\\\\')
  rizin_incdir = fs.as_posix(rizin_incdir).replace('/', '\\\\')
  rizin_datdir = fs.as_posix(rizin_datdir).replace('/', '\\\\')
  rizin_datdir_rz = fs.as_posix(rizin_datdir).replace('/', '\\\\')
else
  rizin_datdir_rz = rizin_datdir / 'rizin'
endif

load_dirs = {
  'rizin_sdb': rizin_datdir_rz,
  'rizin_wwwroot': rizin_datdir_rz / 'www',
  'rizin_sigdb': rizin_datdir_rz / 'sigdb',
  'rizin_themes': rizin_datdir_rz / 'cons',
  'rizin_fortunes': rizin_datdir_rz / 'fortunes',
  'rizin_flags': rizin_datdir_rz / 'flag',
  'rizin_hud': rizin_datdir_rz / 'hud',
  'rizin_plugins': rizin_libdir / 'rizin' / 'plugins',
  'rizin_bindings': rizin_libdir / 'rizin-bindings',
}

# Calcualte BINDIR's depth to be able to find the root directory during runtime
# in portable builds
py_cmd = 'import os; from pathlib import Path; print(len(Path(os.path.normpath(r"@0@")).parents) - len(Path(os.path.normpath(r"@1@")).parents))'.format(rizin_prefix / rizin_bindir, rizin_prefix)
py_cmd = run_command(py3_exe, '-c', py_cmd, check: true)
bindir_depth = py_cmd.stdout().strip()

foreach load_dir, default_subdir : load_dirs
  val = get_option(load_dir)
  if val != ''
    set_variable(load_dir, val)
  else
    val = default_subdir
    if host_machine.system() == 'windows'
      val = fs.as_posix(val).replace('/', '\\\\')
    endif
    set_variable(load_dir, val)
  endif
endforeach

cmake_package_relative_path = run_command(py3_exe, cmake_package_prefix_dir_py, rizin_prefix, rizin_cmakedir / 'xxx', check: true).stdout().strip()

subproject_clean_error_msg = 'Subprojects are not updated. Please run `git clean -dxff subprojects/` to delete all local subprojects directories. If you want to compile against current subprojects then set option `subprojects_check=false`.'

# handle capstone dependency
capstone_dep = dependency('capstone', version: '>=4.0.2', required: get_option('use_sys_capstone'), static: is_static_build)
if not capstone_dep.found()
  capstone_version = get_option('use_capstone_version')
  if fs.is_file('subprojects/capstone-' + capstone_version + '.wrap')
    r = run_command(py3_exe, check_meson_subproject_py, 'capstone-' + capstone_version, check: false)
    if r.returncode() == 1 and get_option('subprojects_check')
      error(subproject_clean_error_msg)
    endif
    capstone_proj = subproject('capstone-' + capstone_version, default_options: ['default_library=static'])
  else
    error('Wrong capstone version selected. Please use one of the supported versions.')
  endif
  capstone_dep = capstone_proj.get_variable('capstone_dep')
else
  # Package managers CS version has no meta programming macros for AArch64 -> ARM64 renaming
  # (because it is outdated).
  # With this define we includeour copy of those macros.
  add_project_arguments(['-DUSE_SYS_CAPSTONE'], language: 'c')
endif

# Handle PCRE2
cpu_jit_supported = [ 'aarch64', 'arm', 'mips', 'mips64', 'ppc', 'ppc64', 'riscv32', 'riscv64', 's390x', 'x86', 'x86_64' ]
pcre2_jit_supported = target_machine.cpu_family() in cpu_jit_supported and cc.get_id() != 'tcc' and target_machine.system() != 'darwin'
if pcre2_jit_supported
  add_project_arguments(['-DSUPPORTS_PCRE2_JIT'], language: 'c')
endif

pcre2_dep_opt = get_option('use_sys_pcre2')
pcre2_dep = disabler()
sys_pcre2_found = false
if (pcre2_dep_opt.enabled() or pcre2_dep_opt.auto()) and not meson.is_cross_build()
  pcre2_deps = []
  sys_pcre2_found = true
  foreach utf : [8, 16, 32]
    pcre2_utf_dep = dependency('libpcre2-@0@'.format(utf), required: false, static: false)
    if not pcre2_utf_dep.found()
      sys_pcre2_found = false
    endif
    pcre2_deps += pcre2_utf_dep
  endforeach
  if sys_pcre2_found
    pcre2_dep = declare_dependency(dependencies: pcre2_deps, version: pcre2_deps[0].version())
  else
    pcre2_dep = cc.find_library('pcre2', required: true, static: true)
  endif
else
  pcre2_dep = dependency('pcre2', version: '>=10.42', required: true, static: true)
endif

if meson.is_cross_build()
  pcre2_native_dep = dependency('pcre2_cross_native', required: true, static: true, native: true)
endif

# handle magic library
sys_magic_opt = get_option('use_sys_magic')
sys_magic = disabler()
if sys_magic_opt.enabled() or sys_magic_opt.auto()
  sys_magic = dependency('libmagic', required: false, static: is_static_build)
  if not sys_magic.found()
    sys_magic = cc.find_library('magic', required: sys_magic_opt, static: is_static_build)
  endif
endif

# Handle Zydis library
sys_zydis_opt = get_option('use_sys_zydis')
zydis_dep = disabler()
if sys_zydis_opt.enabled() or sys_zydis_opt.auto()
  zydis_dep = dependency('zydis', required: false, static: is_static_build)
  if not zydis_dep.found()
    zydis_dep = cc.find_library('zydis', required: sys_zydis_opt, static: is_static_build)
  endif
endif
if (sys_zydis_opt.auto() and not zydis_dep.found()) or sys_zydis_opt.disabled()
  zydis_proj = subproject('zydis', default_options: ['default_library=static'])
  zydis_dep = zydis_proj.get_variable('zydis_dep')
  add_project_arguments(['-DZYDIS_STATIC_BUILD'], language: 'c')
endif


# handle libmspack library
sys_libmspack_opt = get_option('use_sys_libmspack')
libmspack_dep = disabler()
if sys_libmspack_opt.enabled() or sys_libmspack_opt.auto()
  libmspack_dep = dependency('libmspack', required: false, static: is_static_build)
  if not libmspack_dep.found()
    libmspack_dep = cc.find_library('mspack', required: sys_libmspack_opt, static: is_static_build)
  endif
endif
if (sys_libmspack_opt.auto() and not libmspack_dep.found()) or sys_libmspack_opt.disabled()
  libmspack_proj = subproject('libmspack', default_options: ['default_library=static'])
  libmspack_dep = libmspack_proj.get_variable('libmspack_dep')
endif

# handle xxhash library
r = run_command(py3_exe, check_meson_subproject_py, 'xxhash', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

sys_xxhash_opt = get_option('use_sys_xxhash')
if sys_xxhash_opt.enabled() or sys_xxhash_opt.auto()
  xxhash_dep = dependency('libxxhash', required: false, static: is_static_build)
  if not xxhash_dep.found()
    xxhash_dep = cc.find_library('xxhash', required: sys_xxhash_opt, static: is_static_build)
  endif
endif
if (sys_xxhash_opt.auto() and not xxhash_dep.found()) or sys_xxhash_opt.disabled()
  xxhash_proj = subproject('xxhash', default_options: ['default_library=static'])
  xxhash_dep = xxhash_proj.get_variable('xxhash_dep')
endif

# handle libdemangle
r = run_command(py3_exe, check_meson_subproject_py, 'libdemangle', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

libdemangle_options = [
  'default_library=static',
  'use_swift_demangler=false',
]
if get_option('use_gpl')
  libdemangle_options += 'use_gpl=true'
else
  libdemangle_options += 'use_gpl=false'
endif

libdemangle_proj = subproject('libdemangle', default_options: libdemangle_options)
libdemangle_dep = libdemangle_proj.get_variable('libdemangle_dep')

# handle blake3 algo
blake3_dep = dependency('libblake3', required: get_option('use_sys_blake3'), static: is_static_build)
if not blake3_dep.found()
  blake3_proj = subproject('blake3', default_options: ['default_library=static', 'werror=false'])
  blake3_dep = blake3_proj.get_variable('blake3_dep')
endif

# handle openssl library
sys_openssl = dependency('openssl', required: get_option('use_sys_openssl'), static: is_static_build)

# handle tree-sitter
r = run_command(py3_exe, check_meson_subproject_py, 'tree-sitter', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

tree_sitter_dep = dependency('tree-sitter', required: get_option('use_sys_tree_sitter'), static: is_static_build, fallback: [])
if not tree_sitter_dep.found()
  tree_sitter_proj = subproject('tree-sitter', default_options: ['default_library=static'])
  tree_sitter_dep = tree_sitter_proj.get_variable('tree_sitter_dep')
  meson.override_dependency('tree-sitter', tree_sitter_dep)
endif

# handle rizin-grammar-c
r = run_command(py3_exe, check_meson_subproject_py, 'rizin-grammar-c', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

rizin_grammar_c_proj = subproject('rizin-grammar-c', default_options: ['default_library=static', 'werror=false'])
rizin_grammar_c_dep = rizin_grammar_c_proj.get_variable('rizin_grammar_c_dep')
# override done in rizin-grammar-c subproject

has_debugger = get_option('debugger')
have_ptrace = not ['windows', 'cygwin', 'sunos', 'haiku'].contains(host_machine.system())
use_ptrace_wrap = ['linux'].contains(host_machine.system())

have_ptrace = have_ptrace and has_debugger
use_ptrace_wrap = use_ptrace_wrap and has_debugger

message('HAVE_PTRACE: @0@'.format(have_ptrace))
message('USE_PTRACE_WRAP: @0@'.format(use_ptrace_wrap))

checks_level = get_option('checks_level')
message('RZ_CHECKS_LEVEL: @0@'.format(checks_level))
if checks_level < 0 or checks_level > 3
  error('`checks_level` must be between 0 and 3.')
endif

userconf = configuration_data()
ccs = [[cc, userconf, host_machine, false]]
if meson.is_cross_build()
  userconf_native = configuration_data()
  ccs += [[cc_native, userconf_native, build_machine, true]]
endif
userconfs = []

foreach it : ccs
  it_cc = it[0]
  it_userconf = it[1]
  it_machine = it[2]
  it_native = it[3]

  # required for linux
  it_utl = it_cc.find_library('util', required: false, static: is_static_build)
  it_ldl = it_cc.find_library('dl', required: false, static: is_static_build)
  it_th = dependency('threads', required: false, static: is_static_build, native: it_native)
  have_pthread = it_th.found() and it_machine.system() != 'windows'
  if it_machine.system() == 'sunos'
    # workaround for Solaris until https://github.com/mesonbuild/meson/issues/4328 is fixed
    it_mth = declare_dependency(link_args: '-lm')
  else
    it_mth = it_cc.find_library('m', required: false, static: is_static_build)
  endif
  it_lrt = dependency('', required: false, native: it_native)
  if not it_cc.has_function('clock_gettime', prefix: '#include <time.h>') and it_cc.has_header_symbol('features.h', '__GLIBC__')
    it_lrt = it_cc.find_library('rt', required: true, static: is_static_build)
  endif
  have_lrt = not ['windows', 'darwin', 'openbsd', 'android', 'haiku'].contains(it_machine.system())
  if have_lrt and not it_lrt.found()
    it_lrt = it_cc.find_library('rt', required: true, static: is_static_build)
  endif
  # FreeBSD has backtrace() in libexecinfo
  it_lexecinfo = it_cc.find_library('execinfo', required: false)

  it_userconf.set('RZ_CHECKS_LEVEL', checks_level)
  it_userconf.set10('IS_PORTABLE', get_option('portable'))
  it_userconf.set10('HAVE_LIB_MAGIC', sys_magic.found())
  it_userconf.set10('USE_LIB_MAGIC', sys_magic.found())
  it_userconf.set10('HAVE_LIB_PCRE2', pcre2_dep.found())
  it_userconf.set10('USE_LIB_PCRE2', pcre2_dep.found())
  it_userconf.set10('HAVE_LIB_XXHASH', xxhash_dep.found())
  it_userconf.set10('USE_LIB_XXHASH', xxhash_dep.found())
  it_userconf.set10('DEBUGGER', has_debugger)
  if extra_prefix != ''
    it_userconf.set_quoted('EXTRA_PREFIX', extra_prefix)
  else
    it_userconf.set10('EXTRA_PREFIX', false)
  endif
  it_userconf.set('PREFIX', rizin_prefix)
  it_userconf.set('BINDIR', rizin_bindir)
  it_userconf.set('BINDIR_DEPTH', bindir_depth)
  it_userconf.set('LIBDIR', rizin_libdir)
  it_userconf.set('INCLUDEDIR', rizin_incdir)
  it_userconf.set('DATADIR_RZ', rizin_datdir_rz)
  is_ppc = it_machine.cpu_family() == 'ppc' or it_machine.cpu_family() == 'ppc64'
  it_userconf.set10('HAVE_JEMALLOC', it_machine.system() != 'windows' and (it_machine.system() != 'darwin' or not is_ppc))
  it_userconf.set('DATADIR', rizin_datdir)
  it_userconf.set('WWWROOT', rizin_wwwroot)
  it_userconf.set('SDB', rizin_sdb)
  it_userconf.set('SIGDB', rizin_sigdb)
  it_userconf.set('THEMES', rizin_themes)
  it_userconf.set('FORTUNES', rizin_fortunes)
  it_userconf.set('FLAGS', rizin_flags)
  it_userconf.set('HUD', rizin_hud)
  it_userconf.set('PLUGINS', rizin_plugins)
  it_userconf.set('BINDINGS', rizin_bindings)
  it_userconf.set10('HAVE_OPENSSL', sys_openssl.found())
  it_userconf.set10('WANT_DYLINK', true)
  it_userconf.set10('HAVE_PTRACE', have_ptrace)
  it_userconf.set10('USE_PTRACE_WRAP', use_ptrace_wrap)
  it_userconf.set10('WITH_GPL', get_option('use_gpl'))
  it_userconf.set10('RZ_BUILD_DEBUG', get_option('buildtype').startswith('debug'))
  ok = it_cc.has_header_symbol('sys/personality.h', 'ADDR_NO_RANDOMIZE')
  it_userconf.set10('HAVE_DECL_ADDR_NO_RANDOMIZE', ok)
  ok = it_cc.has_header_symbol('sys/procctl.h', 'PROC_ASLR_CTL')
  it_userconf.set10('HAVE_DECL_PROCCTL_ASLR_CTL', ok)
  it_userconf.set10('HAVE_THREADS', it_th.found())
  it_userconf.set10('HAVE_PTHREAD', have_pthread)
  it_userconf.set10('HAVE_LZMA', get_option('use_lzma'))
  it_userconf.set10('HAVE_ZLIB', get_option('use_zlib'))
  it_userconf.set10('USE_SYS_ZYDIS', zydis_dep.found() and zydis_dep.type_name() != 'internal')
  it_userconf.set10('SUPPORTS_PCRE2_JIT', pcre2_jit_supported)
  it_userconf.set('N_THREAD_LIMIT', get_option('n_thread_limit'))

  if it_machine.system() == 'freebsd' or it_machine.system() == 'dragonfly'
    add_project_link_arguments('-Wl,--unresolved-symbols,ignore-in-object-files', language: 'c', native: it_native)
    add_project_link_arguments('-Wl,--allow-shlib-undefined', language: 'c', native: it_native)
  endif

  if it_machine.system() == 'darwin'
    # On older Mac OS X versions (at least Lion and older), environ is only available when compiling an executable,
    # but we will use is primarily in shared libs. Unfortunately, it_cc.links() only checks for executable compiling,
    # so this does not help us.
    # But in fact, even the environ(7) man page on macOS 11.6 still claims that environ is not avaliable for
    # shared libs (even though it seems to work) and suggests using _NSGetEnviron(), so let's just do this always.
    have_environ = false
  elif host_machine.system() == 'windows'
    code = '#include <stdlib.h>\n\nint main() { char **env = environ; }'
    have_environ = cc.links(code, name: 'have environ')
  else
    code = '#include <unistd.h>\nextern char **environ;\nint main() { char **env = environ; }'
    have_environ = it_cc.links(code, name: 'have extern char **environ')
  endif
  it_userconf.set10('HAVE_ENVIRON', have_environ)
  message('HAVE_ENVIRON: @0@'.format(have_environ))

  code = '#if __is_target_os(ios)\nint x = 0;\n#endif\nint main() { x++; }'
  is_ios = it_cc.links(code, name: 'target is ios')
  it_userconf.set10('IS_IOS', is_ios)
  message('IS_IOS: @0@'.format(is_ios))

  foreach item : [
      ['arc4random', '#include <stdlib.h>', []],
      ['arc4random_uniform', '#include <stdlib.h>', []],
      ['explicit_bzero', '#include <string.h>', []],
      ['explicit_memset', '#include <string.h>', []],
      ['clock_nanosleep', '#include <time.h>', []],
      ['clock_gettime', '#include <time.h>', [it_lrt]],
      ['sigaction', '#include <signal.h>', []],
      ['pipe', '#include <unistd.h>', []],
      ['execv', '#include <unistd.h>', []],
      ['execve', '#include <unistd.h>', []],
      ['execvp', '#include <unistd.h>', []],
      ['execl', '#include <unistd.h>', []],
      ['system', '#include <stdlib.h>', []],
      ['realpath', '#include <stdlib.h>', []],
      ['fork', '#include <unistd.h>', []],
      ['nice', '#include <unistd.h>', []],
      ['copyfile', '#include <copyfile.h>', []],
      ['strlcpy', '#include <string.h>', []],
      ['strlcat', '#include <string.h>', []],
      ['strnlen', '#include <string.h>', []],
      ['shm_open', '#include <sys/mman.h>', [it_lrt]],
      ['openpty', '', [it_utl]],
      ['forkpty', '', [it_utl]],
      ['login_tty', '', [it_utl]],
      ['pipe2', '#define _GNU_SOURCE\n#include <fcntl.h>\n#include <unistd.h>', []],
      # copy_file_range for now disable on freebsd as it s not reliable even for small chunks
      ['copy_file_range', '#ifdef __linux__\n#define _GNU_SOURCE\n#include <unistd.h>\n#endif', []],
      ['backtrace', '', [it_lexecinfo]],
      ['__builtin_bswap16', '', []],
      ['__builtin_bswap32', '', []],
      ['__builtin_bswap64', '', []],
      ['__builtin_clzll', '', []],
      ['__builtin_ctzll', '', []],
      ['posix_memalign', '#include <stdlib.h>', []],
      ['_aligned_malloc', '#include <malloc.h>', []],
    ]
    func = item[0]
    ok = it_cc.has_function(func, prefix: item[1], dependencies: item[2])
    it_userconf.set10('HAVE_@0@'.format(func.to_upper()), ok)
  endforeach

  foreach item : [
      ['linux/ashmem.h', '', []],
      ['sys/shm.h', '', []],
      ['sys/ipc.h', '', []],
      ['sys/mman.h', '', []],
      ['inttypes.h', '', []],
    ]
    hdr = item[0]
    ok = it_cc.has_header(hdr, prefix: item[1], dependencies: item[2])
    it_userconf.set10('HAVE_HEADER_@0@'.format(hdr.underscorify().to_upper()), ok)
  endforeach

  if it_userconf.get('HAVE_PIPE2') == 1
    add_project_arguments('-D_GNU_SOURCE', language: ['c', 'cpp'], native: it_native)
  endif
  userconfs += [[it_userconf, it_lrt, it_utl, it_ldl, it_mth, it_th]]
endforeach
userconf = userconfs[0][0]
lrt = userconfs[0][1]
utl = userconfs[0][2]
ldl = userconfs[0][3]
mth = userconfs[0][4]
th = userconfs[0][5]
if meson.is_cross_build()
  userconf_native = userconfs[1][0]
  lrt_native = userconfs[1][1]
  utl_native = userconfs[1][2]
  ldl_native = userconfs[1][3]
  mth_native = userconfs[1][4]
  th_native = userconfs[1][5]
endif

rz_userconf_h_in = files('librz/include/rz_userconf.h.in')
rz_userconf_h = configure_file(
  input: rz_userconf_h_in,
  output: 'rz_userconf.h',
  configuration: userconf,
  install_dir: rizin_incdir
)

packager = get_option('packager')
packager_version = get_option('packager_version')

message('Version Major: @0@0'.format(rizin_version_major))
message('Version Minor: @0@0'.format(rizin_version_minor))
message('Version Patch: @0@0'.format(rizin_version_patch))

versionconf = configuration_data()
versionconf.set('RZ_VERSION_MAJOR', rizin_version_major)
versionconf.set('RZ_VERSION_MINOR', rizin_version_minor)
versionconf.set('RZ_VERSION_PATCH', rizin_version_patch)
versionconf.set('RZ_VERSION_NUMBER', rizin_version_number)
versionconf.set('RZ_VERSION', rizin_version)
if packager_version != ''
  versionconf.set_quoted('RZ_PACKAGER_VERSION', packager_version)
  if packager != ''
    versionconf.set_quoted('RZ_PACKAGER', packager)
  endif
endif
rz_version_h = configure_file(
  input: 'librz/include/rz_build_version.h.in',
  output: 'rz_build_version.h',
  configuration: versionconf,
  install_dir: rizin_incdir
)

if git_exe.found() and fs.exists('.git')
  gittip_file = custom_target('gittip_file',
    build_always_stale: true,
    build_by_default: true,
    output: 'gittip',
    command: [py3_exe, git_exe_repo_py, git_exe, repo, '@OUTPUT@', 'rev-parse', 'HEAD'],
    install: true,
    install_dir: rizin_datdir_rz
  )
endif

# handle zlib dependency
zlib_dep = disabler()
if get_option('use_zlib')
  r = run_command(py3_exe, check_meson_subproject_py, 'zlib', check: false)
  if r.returncode() == 1 and get_option('subprojects_check')
    error(subproject_clean_error_msg)
  endif

  zlib_dep = dependency('zlib', required: get_option('use_sys_zlib'), static: is_static_build)
  if not zlib_dep.found()
    zlib_proj = subproject('zlib', default_options: ['default_library=static', 'werror=false'])
    zlib_dep = zlib_proj.get_variable('zlib_dep')
    meson.override_dependency('zlib', zlib_dep)
  endif
endif

# handle lz4 dependency
r = run_command(py3_exe, check_meson_subproject_py, 'lz4', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

lz4_dep = dependency('liblz4', required: get_option('use_sys_lz4'), static: is_static_build)
if not lz4_dep.found()
  lz4_proj = subproject('lz4', default_options: ['default_library=static'])
  lz4_dep = lz4_proj.get_variable('lz4_dep')
endif

# handle lzma dependency
liblzma_dep = disabler()
if get_option('use_lzma')
  r = run_command(py3_exe, check_meson_subproject_py, 'lzma', check: false)
  if r.returncode() == 1 and get_option('subprojects_check')
    error(subproject_clean_error_msg)
  endif

  liblzma_dep = dependency('liblzma', required: get_option('use_sys_lzma'), static: is_static_build)
  if not liblzma_dep.found()
    liblzma_proj = subproject('liblzma', default_options: ['default_library=static'])
    liblzma_dep = liblzma_proj.get_variable('lzma_dep')
    meson.override_dependency('liblzma', liblzma_dep)
  endif
endif

# handle softfloat dependency
r = run_command(py3_exe, check_meson_subproject_py, 'softfloat', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

softfloat_dep = dependency('softfloat', required: get_option('use_sys_softfloat'), static: is_static_build)
if not softfloat_dep.found()
  softfloat_proj = subproject('softfloat', default_options: ['default_library=static'])
  softfloat_dep = softfloat_proj.get_variable('softfloat_dep')
endif

if meson.is_cross_build()
  softfloat_cross_native_proj = subproject('softfloat_cross_native', default_options: ['default_library=static', 'cross_native=true'])
  softfloat_cross_native_dep = softfloat_cross_native_proj.get_variable('softfloat_dep')
endif

# handle zip dependency
r = run_command(py3_exe, check_meson_subproject_py, 'libzip', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

libzip_dep = dependency('libzip', required: get_option('use_sys_libzip'), static: is_static_build)
if not libzip_dep.found()
  libzip_proj = subproject('libzip', default_options: [
    'default_library=static',
    'werror=false',
    'static_runtime=@0@'.format(is_static_build),
    'use_sys_openssl=@0@'.format(get_option('use_sys_libzip_openssl')),
  ])
  libzip_dep = libzip_proj.get_variable('libzip_dep')
endif

# handle zstd dependency
r = run_command(py3_exe, check_meson_subproject_py, 'zstd', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

libzstd_dep = dependency('libzstd', required: get_option('use_sys_libzstd'), static: is_static_build, fallback: [])
if not libzstd_dep.found()
  zstd_proj = subproject('zstd', default_options: [
      'default_library=static',
      'static_runtime=@0@'.format(is_static_build),
  ])
  libzstd_dep = zstd_proj.get_variable('libzstd_dep')
  meson.override_dependency('zstd', libzstd_dep)
endif

# handle mpc dependency
r = run_command(py3_exe, check_meson_subproject_py, 'mpc', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

mpc_proj = subproject('mpc', default_options: ['default_library=static'])
mpc_dep = mpc_proj.get_variable('mpc_dep')

# handle yxml dependency
r = run_command(py3_exe, check_meson_subproject_py, 'yxml', check: false)
if r.returncode() == 1 and get_option('subprojects_check')
  error(subproject_clean_error_msg)
endif

yxml_proj = subproject('yxml', default_options: ['default_library=static'])
yxml_dep = yxml_proj.get_variable('yxml_dep')

use_rpath = false
use_rpath_absolute = false
if host_machine.system() != 'windows' and get_option('default_library') == 'shared'
  if get_option('local') == 'enabled'
    use_rpath = true
  elif get_option('local') == 'absolute'
    use_rpath_absolute = true
  elif get_option('local') == 'auto' and get_option('prefix') != '/usr'
    if host_machine.system() == 'openbsd'
      # OpenBSD's $ORIGIN only works in the way we would need it when running an
      # executable by its actual path, but not when run through PATH.
      # So let's use absolute rpaths there.
      use_rpath_absolute = true
    else
      use_rpath = true
    endif
  endif
endif

rpath_exe = ''
rpath_lib = ''
rpath_summary = 'disabled'
if use_rpath
  # Use bindir depth to create a path to the rootdir from bindir
  py_cmd = 'import os; print("../" * @0@)'.format(bindir_depth.to_int())
  py_cmd = run_command(py3_exe, '-c', py_cmd, check: true)
  path_to_rootdir = py_cmd.stdout().strip()

  rpath_exe = '$ORIGIN/' + path_to_rootdir + get_option('libdir')
  rpath_lib = '$ORIGIN'
  rpath_summary = 'relative'
elif use_rpath_absolute
  rpath_exe = get_option('prefix') / get_option('libdir')
  rpath_lib = get_option('prefix') / get_option('libdir')
  rpath_summary = 'absolute'
endif
# NOTE: this is to workaround an issue on Windows where unit tests don't use
# the right libraries, resulting in tests not being run properly
test_env_common_path = []
build_root = meson.current_build_dir()
if host_machine.system() == 'windows'
  test_env_common_path += [
    build_root / 'librz' / 'arch',
    build_root / 'librz' / 'bin',
    build_root / 'librz' / 'config',
    build_root / 'librz' / 'cons',
    build_root / 'librz' / 'signature',
    build_root / 'librz' / 'core',
    build_root / 'librz' / 'demangler',
    build_root / 'librz' / 'diff',
    build_root / 'librz' / 'crypto',
    build_root / 'librz' / 'debug',
    build_root / 'librz' / 'egg',
    build_root / 'librz' / 'flag',
    build_root / 'librz' / 'mark',
    build_root / 'librz' / 'hash',
    build_root / 'librz' / 'io',
    build_root / 'librz' / 'lang',
    build_root / 'librz' / 'magic',
    build_root / 'librz' / 'main',
    build_root / 'librz' / 'parse',
    build_root / 'librz' / 'reg',
    build_root / 'librz' / 'search',
    build_root / 'librz' / 'socket',
    build_root / 'librz' / 'syscall',
    build_root / 'librz' / 'type',
    build_root / 'librz' / 'util',
  ]
endif

subdir('librz')

if get_option('install_sigdb')
  r = run_command(py3_exe, check_meson_subproject_py, 'sigdb', check: false)
  if r.returncode() == 1 and get_option('subprojects_check')
    error(subproject_clean_error_msg)
  endif
  subproject('sigdb', default_options: ['sigdb_path=@0@'.format(get_option('prefix') / rizin_sigdb)])
endif

cli_option = get_option('cli')
cli_enabled = cli_option.auto() ? not meson.is_subproject() : cli_option.enabled()
if cli_enabled
  subdir('binrz')

  install_data('doc/hud',
    install_dir: rizin_hud,
    rename: 'main'
  )
endif

subdir('test')

install_data(
  'doc/fortunes.fun',
  'doc/fortunes.tips',
  install_dir: rizin_fortunes
)

dist_script = 'sys/meson_dist_script.py'
if meson.version().version_compare('>=0.49.0') and meson.version().version_compare('<0.57.0')
  meson.add_dist_script(meson.source_root() / dist_script)
else
  meson.add_dist_script(files(dist_script))
endif

summary({
  'prefix': rizin_prefix,
  'bindir': rizin_bindir,
  'libdir': rizin_libdir,
  'includedir': rizin_incdir,
  'datadir': rizin_datdir,
  'wwwroot': rizin_wwwroot,
  'sdb': rizin_sdb,
  'sigdb': rizin_sigdb,
  'themes': rizin_themes,
  'fortunes': rizin_fortunes,
  'flags': rizin_flags,
  'hud': rizin_hud,
  'plugins': rizin_plugins,
  'bindings': rizin_bindings,
}, section: 'Directories')

summary({
  'GPL code': get_option('use_gpl'),
  'Install sigdb': get_option('install_sigdb'),
  'Debugger enabled': has_debugger,
  'Capstone version': capstone_dep.version(),
  'PCRE2 version': pcre2_dep.version(),
  'PCRE2 JIT': pcre2_jit_supported,
  'System magic library': sys_magic.found() and sys_magic.type_name() != 'internal',
  'System pcre2 library': pcre2_dep.found() and sys_pcre2_found,
  'System xxhash library': xxhash_dep.found() and xxhash_dep.type_name() != 'internal',
  'System libmspack library': libmspack_dep.found() and libmspack_dep.type_name() != 'internal',
  'System openssl library': sys_openssl.found() and sys_openssl.type_name() != 'internal',
  'System capstone library': capstone_dep.found() and capstone_dep.type_name() != 'internal',
  'System zydis library': zydis_dep.found() and zydis_dep.type_name() != 'internal',
  'System tree-sitter library': tree_sitter_dep.found() and tree_sitter_dep.type_name() != 'internal',
  'System lz4 library': lz4_dep.found() and lz4_dep.type_name() != 'internal',
  'System lzma library': liblzma_dep.found() and liblzma_dep.type_name() != 'internal',
  'System softfloat library': softfloat_dep.found() and softfloat_dep.type_name() != 'internal',
  'System zlib library': zlib_dep.found() and zlib_dep.type_name() != 'internal',
  'System zstd library': libzstd_dep.found() and libzstd_dep.type_name() != 'internal',
  'System zip library': libzip_dep.found() and libzip_dep.type_name() != 'internal',
  'System blake3 library': blake3_dep.found() and blake3_dep.type_name() != 'internal',
  'Use ptrace-wrap': use_ptrace_wrap,
  'Use RPATH': rpath_summary,
}, section: 'Configuration', bool_yn: true)

# output will be slightly degraded if there's only 1 crca_crc hash plugin and/or last hash plugin is a crca_crc plugin
comb_hash_plugins = []
in_crca_loop = false
foreach plugin : hash_plugins.get('list')
  if plugin.startswith('crca_crc')
    comb_hash_plugins += (in_crca_loop ? '' : 'crca_crc(') + plugin.split('_')[1].split('crc')[1]
    in_crca_loop = true
  else
    if in_crca_loop
      # add closing parenthesis to last crca_cec plugin
      parenthesized_last = []
      foreach item : comb_hash_plugins
        if item not in [comb_hash_plugins[-1]]
          parenthesized_last += item
        endif
      endforeach
      parenthesized_last += comb_hash_plugins[-1] + ')'
      comb_hash_plugins = parenthesized_last
    endif
    comb_hash_plugins += plugin
    in_crca_loop = false
  endif
endforeach

summary({
  'Arch Plugins': arch_plugins.get('list'),
  'Binary Plugins': bin_plugins.get('list'),
  'BinXtr Plugins': bin_xtr_plugins.get('list'),
  'Core Plugins': core_plugins.get('list'),
  'Crypto Plugins': crypto_plugins.get('list'),
  'Debug Plugins': debug_plugins.get('list'),
  'Egg Plugins': egg_plugins.get('list'),
  'IO Plugins': io_plugins.get('list'),
  'Lang Plugins': lang_plugins.get('list'),
  'Hash Plugins': comb_hash_plugins,
  'Demangler Plugins': demangler_plugins.get('list'),
}, section: 'Plugins', list_sep: ', ')
